package cn.bugstack.domain.strategy.service.armory;

import cn.bugstack.domain.strategy.model.entity.StrategyAwardEntity;
import cn.bugstack.domain.strategy.model.entity.StrategyEntity;
import cn.bugstack.domain.strategy.model.entity.StrategyRuleEntity;
import cn.bugstack.domain.strategy.repository.IStrategyRepository;
import cn.bugstack.types.enums.ResponseCode;
import cn.bugstack.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.security.SecureRandom;
import java.util.*;
import java.util.stream.Collectors;

/**
 * TODO
 *
 * @Description
 * @Author Lx
 * @Date 2024/7/29 0:31
 **/
@Slf4j
@Service
public class StrategyArmoryDispatch implements IStrategyArmory,IStrategyDispatch{

    @Resource
    private IStrategyRepository strategyRepository;



    @Override
    public boolean assembleLotteryStrategy(Long strategyId) {
        //1.装配的策略的信息
        //1.1.根据策略ID查询策略奖品信息列表
        List<StrategyAwardEntity> strategyAwardEntities = strategyRepository.queryStrategyAwardList(strategyId);
        //1.2.装配策略奖品进策略奖品表(落库和放到缓存中)
        assembleLotteryStrategy(String.valueOf(strategyId),strategyAwardEntities);


        //2.根据策略ID拿到策略model名称字段
        //2.1.拿到策略业务实体
        StrategyEntity strategyEntity = strategyRepository.queryStrategyEntityByStrategyId(strategyId);
        //2.2.从中拿出策略规则名称字段
        String ruleWeight = strategyEntity.getRuleWeight();
        //2.3.判断策略规则是否为空
        if(null == ruleWeight){
            return true;//没有规则则直接返回true
        }

        //3.根据策略规则名称拿到详细规则
        StrategyRuleEntity strategyRuleEntity = strategyRepository.queryStrategyRuleByStrategyIdAndRuleName(strategyId,ruleWeight);
        if(null == strategyRuleEntity){
            //规则详情为空
            throw new AppException(ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getCode(),ResponseCode.STRATEGY_RULE_WEIGHT_IS_NULL.getInfo());
        }
        //4.解析出详细规则中的规则列表
        Map<String,List<Integer>> ruleWeightValueMap = strategyRuleEntity.getRuleWeightValues();
        //5.根据每个key拿到其对应的权重列表(遍历规则列表，拿到各个key的对应的value)，然后对每个策略奖品列表进行装配策略(将一开始的根据策略ID+策略奖品这种kv来装配，现在改进成了策略ID_策略规则(含kv但没解析)+策略规则项对应的权重的列表这种kv
        for (Map.Entry<String, List<Integer>> entry : ruleWeightValueMap.entrySet()) {
            //拿到权重列表
            List<Integer> ruleWeightValues = ruleWeightValueMap.get(entry.getKey());
            //封装策略
            ////拿到一开始查到的该策略ID对应的所有策略奖品
            ArrayList<StrategyAwardEntity> strategyAwardEntitiesClone = new ArrayList<>(strategyAwardEntities);
            ////根据规则列表中各个的key值和value权重表，来装配对应的key.(去除掉不是该key的策略奖品)
            strategyAwardEntitiesClone.removeIf(entity -> !ruleWeightValues.contains(entity.getAwardId()));
            //装配策略
            assembleLotteryStrategy(String.valueOf(strategyId).concat("_").concat(entry.getKey()),strategyAwardEntitiesClone);
        }

        return true;
    }


    //根据策略ID 和 策略奖品列表进行装配策略
    private void assembleLotteryStrategy(String strategyId,List<StrategyAwardEntity> strategyAwardEntities){
        //1.获取最小的奖品概率值
        BigDecimal minAwardRate = strategyAwardEntities.stream().map(StrategyAwardEntity::getAwardRate).min(BigDecimal::compareTo).orElse(BigDecimal.ZERO);//拿最小，拿不到数 返回0

        //2.计算总的奖品概率值的总和
        BigDecimal totalAwardRate = strategyAwardEntities.stream().map(StrategyAwardEntity::getAwardRate).reduce(BigDecimal.ZERO,BigDecimal::add);//处理：加和到0上去，求和

        //3.计算抽奖概率范围(用概率总和/最小概率值)即可得到抽奖的概率的范围。
        // 举例：概率总和是2+3+4+5=14,minRate=2;则14/2=7，可以分为7份，然后minRate占一份，即以最小的概率作为单位
        BigDecimal RateRange = totalAwardRate.divide(minAwardRate,0,BigDecimal.ROUND_CEILING);//小数点保留0位，并且向上取整
        //4.根据概率范围去加入策略奖品到策略奖品表中去
        List<Integer> strategyAwardRateList = new ArrayList<>(RateRange.intValue());//策略奖品表
        //将查询到的奖品给根据rate添加到策略奖品表当中去
        for (StrategyAwardEntity strategyAwardEntity : strategyAwardEntities) {
            Integer awardId= strategyAwardEntity.getAwardId();
            BigDecimal rate = strategyAwardEntity.getAwardRate();
            //获取到rate所对应的在策略奖品表中占的份数，然后把奖品添加进去
            for(int i=0;i<rate.multiply(RateRange).setScale(0,BigDecimal.ROUND_CEILING).intValue();i++){
                strategyAwardRateList.add(awardId);
            }
        }

        //5.打乱策略奖品表，因为我们是从里面抽的
        Collections.shuffle(strategyAwardRateList);

        //6.把奖品ID的tableList转变成tableMap,方便我们放到redis中去
        Map<Integer,Integer> shuffleStrategyAwardSearchRateMap = new LinkedHashMap<>();
        for (int i=0;i<strategyAwardRateList.size();i++){
            shuffleStrategyAwardSearchRateMap.put(i,strategyAwardRateList.get(i));
        }


        //8.通过repository存放到redis中
        strategyRepository.storeStrategyAwardSearchRateTable(Long.valueOf(strategyId),shuffleStrategyAwardSearchRateMap.size(),shuffleStrategyAwardSearchRateMap);
    }

    @Override
    public Integer getRandomAwardId(Long strategyId) {
        //1.根据策略ID获取策略的抽奖次数范围
        int rateRange = strategyRepository.getRateRange(strategyId);
        //2.根据策略ID和概率范围生成的随机数(生成在概率范围内的随机的值)，然后去奖品概率表中get出来
        int AwardId = strategyRepository.getStrategyAwardAssemble(strategyId,new SecureRandom().nextInt(rateRange));
        return AwardId;
    }

    @Override
    public Integer getRandomAwardId(Long strategyId, String ruleWeightValue) {
        String cacheKey = String.valueOf(strategyId).concat("_").concat(ruleWeightValue);//根据策略ID_策略权重，从缓存中拿取出策略奖品的概率范围
        int rateRange = strategyRepository.getRateRange(cacheKey);
        //根据策略ID和概率范围内生成的随机数进行抽奖
        int AwardId = strategyRepository.getStrategyAwardAssemble(strategyId,new SecureRandom().nextInt(rateRange));
        return AwardId;

    }

    @Override
    public Boolean subtractionAwardStock(Long strategyId, Integer awardId) {
        return null;
    }

    @Override
    public void sendDeductStockMsg(Long strategyId, Integer awardId) {
        strategyRepository.sendDeductStockMsg(strategyId,awardId);
    }
}
